package cz.pecinovsky.česky.oopnz.ufo;
/*******************************************************************************
 *<pre>
 * Předchozí:   Není - toto_je_nově_definovaná_třída
 *              Ttt v projektu Ppp
 * Následující: Prozatím není
 *
 * Projekt  Ppp
 *   + Přidáno
 *   - Odebráno
 *   ~ Změněno
 *</pre>
 */

import cz.pecinovsky.česky.oopnz.utility.Barva;


////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ Konec ignorovaného počátečního textu +++++

import java.awt.geom.Ellipse2D;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;


/*******************************************************************************
 * Třída {@code Dispečer} má na starosti vedení hry,
 * při které má uživatel dopravit UFO ze startovní rampy do hangáru.
 * <p>
 * V první variantě uživatel zadává rychlost UFO přímým voláním metody
 * setRychlost(double,double).
 * <p>
 * Při druhé varianty hry, uživatel zadává tah motorů a tím i zrychlení UFO
 * prostřednictvím kurzorových kláves a mezerníku, kterým motory vypíná.
 * Stiskem číselné klávesy může přepínat které UFO zrovna ovládá.
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Dispečer
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Velikost (=průměr) talíře je mírou všech rozměrů ve vesmíru. */
    public static final int VELIKOST = 20;
//
//    /** Náhrada za přepisování Talíř.VELIKOST pro lenochy. */
//    protected static final int TV = VELIKOST;

    /** Frekvence snímkování = počet překreslení Vesmíru za vteřinu. */
    public static final int FREKVENCE = 8;

    /** Odkaz na okno, ve kterém se všechno kreslí. */
    protected static final Vesmír V = Vesmír.getVesmír();
//%I+ <0
//
//    /** Nejmenší povolená velikost překážející planety. */
//    private static final int NEJMENŠÍ = VELIKOST;
//
//    /** Největší povolená velikost překážející planety. */
//    private static final int NEJVĚTŠÍ = NEJMENŠÍ * 4;
//%I-

/** Vektor s možnými barvami planet. První tři ale nebudeme používat.
     *  Černá [0] je barva vesmíru, takže by nebyla vidět,
     *  Modrá [1] bude barva startovní rampy a hangárů a
     *  Červená [2] bude barva létajících talířů. */
    public  static final Barva   barvaVesmíru;
    public  static final Barva   barvaRampy;
    public  static final Barva   barvaUFO;
//%I+ <0
//    private static final Barva[] barvyPlanet;
//%I-

    

//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================

    private static Dispečer dispečer;



//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
    
    static
    {
        barvaVesmíru = Barva.ČERNÁ;
        barvaRampy   = Barva.AZUROVÁ;
        barvaUFO     = Barva.ČERVENÁ;
//%I+ <0
//        barvyPlanet  = new Barva[ b.length-3 ];
//        System.arraycopy( b, 3, barvyPlanet, 0, barvyPlanet.length );
//        b = null;
//%I-
    }

//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    /** Továrna, která bude vyrábět požadovaná UFO. */
    private final ITovárnaNaUFO továrna;

    /** Počet hangárů, do nichž je třeba postupně umístit jednotlivá UFO. */
    private final int HANGÁRŮ;

    /** Startovací rampa (nultá položka) a přistávací rampy = hangáry. */
    private final Rampa[]   rampa;
//%I+ <0
//    /** Planety, které mají za úkol překážet v rychlém dosažení cíle. */
//    private final Planeta[] planeta;
//
//%I-

    /** Seznam doposud vygenerovaných UFO. */
    private final List<IUFO> ufoList = new ArrayList<IUFO>();

    /** Časovač, který zabezpečí pravidelné, opakované vyvolání metody
     *  run() puštěného filmu. */
    private final Timer timer = new Timer("Dispečer UFO");

    /** Indikuje, zda je v provozu verze ovládané z klávesnice
     *  nebo verze ovládáná přímým voláním metody setRychlost(int,int) */
    private final boolean zKlávesnice;

    /** Svislá souřadnice hangárů. */
    private final int yHangáru;



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** Aktuální počet vygenerovaných UFO. */
    protected int početUFO = 0;

    /** Objekt, který ma na starosti korektní zobrazování pohybujících se
     *  objektů. */
    private Film film = null;

    /** Proměnná registrující běh filmu -
     *  zabezpečuje, aby nebylo možno spustit právě běžící film. */
    private boolean stojí = true;

    /** UFO, které je v danou chvíli ovládané klávesnicí. */
    private IUFO ovládané;

    /** Posluchač reagtující na stisky kláves. */
    private Posluchač posluchač;
//%I+ <0
//
//    /** Třída, jejíž instance budou vytvářeny jako UFO. */
//    private Class<? extends UFO> ufoClass = UFO.class;
//
//    /** Konstruktor, kterým budou vytvářena nová UFO. */
//    private Constructor<? extends UFO> ufoKonstruktor;
//    {
//        try
//        {
//            ufoKonstruktor = ufoClass.getConstructor(
//                new Class[] { Talíř.class, int.class } );
//        }
//        catch( Exception e )
//        {
//            throw new RuntimeException(
//                "Nepodařilo se vytvořit konsturktor třídy " + ufoClass, e );
//        }
//    }
//%I-



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Je-li již vytvořen vesmír s dispečerem, vrátí odkaz na dispečera;
     * není-li ještě vytvořen vesmír s dispečerem, vytvoří vesmír,
     * v němž se budou pohybovat UFO, u horního okraje plátna pro ně
     * připraví startovací rampu a u dolního okraje 5 hangárů
     * a odstartuje hru řízenou pomocí klávesnice.
     * 
     * @param továrna Továrna, která bude vyrábět požadovaná UFO
     * @return Odkaz na dispečera
     */
    public static Dispečer getDispečer(ITovárnaNaUFO továrna)
    {
        if( dispečer != null ) {
            return dispečer;
        }
        return getDispečer(továrna, 5, true );
    }


    /***************************************************************************
     * Je-li již vytvořen vesmír s dispečerem, vyvolá výjimku,
     * není-li ještě vytvořen vesmír s dispečerem, vytvoří vesmír,
     * v němž se budou pohybovat UFO a u horního okraje plátna pro ně
     * připraví startovací rampu a u dolního okraje zadaný počet hangárů,
     * zároveň nastaví, bude-li hra ovládaná z klávesnice.
     *
     * @param továrna      Továrna, která bude vyrábět požadovaná UFO
     * @param hangárů      Požadovaný počet hangárů.
     * @param zKlávesnice  {@code true} = hra bude ovládána z klávesnice
     * @return Odkaz na dispečera
     */
    static Dispečer getDispečer(ITovárnaNaUFO továrna, int hangárů,
                                boolean zKlávesnice )
    {
        if( dispečer != null ) {
            throw new IllegalStateException(
                "Může být vytvořen pouze jeden dispečer." );
        }
        dispečer = new Dispečer(továrna, hangárů, zKlávesnice, 0);
        return dispečer;
    }


    /***************************************************************************
     * Vytvoří vesmír, v němž se budou pohybovat UFO a u spodního okraje plátna
     * pro ně připraví zadaný počet hangárů.
     * Požadovaný počet překážejících planet i cílových hangárů
     * může být omezen.
     *
     * @param hangárů      Požadovaný počet hangárů.
     * @param zKlávesnice  {@code true} = hra bude ovládána z klávesnice
     * @param planet       Počet náhodně rozmístěných planet.
     * @return Odkaz na dispečera
     */
    private Dispečer(ITovárnaNaUFO továrna,
                     int hangárů, boolean zKlávesnice, int planet)
    {
        this.továrna     = továrna;
        this.zKlávesnice = zKlávesnice;

        HANGÁRŮ = hangárů;
        int rozměr = 3*HANGÁRŮ*VELIKOST;
        V.setRozměr( rozměr, rozměr );

        rampa   = new Rampa[HANGÁRŮ+1];
//%I+ <0
//        planeta = new Planeta[planet];
//%I-

        /*# Vytvořit nový vesmír, v jehož levém horním rohu bude startovací
         *  rampa (čtverec), kam budou přistavována nová ufo, a podél dolního
         *  okraje bude sada očíslovaných hangárů (opět čtverce), kam budou
         *  ufo přistávat. */
        yHangáru = V.getVýška() - VELIKOST;
        rampa[0] = new Rampa( 0, VELIKOST, VELIKOST );
        for( int h=1,  hx=3*VELIKOST/2;
             h <= HANGÁRŮ;
             h++, hx+=3*VELIKOST )
        {
            rampa[h] = new Rampa( h, hx, yHangáru );
        }

//%I+ <0
//        /*# Vytvořit požadovaný počet planet, kterým se budou muset ufo
//         *  vyhýbat. Planety musí být dostatečně daleko od sebe,
//         *  aby mezi nimi mohlo ufo s rezervou proletět (aby mezi planetami
//         *  bylo místo alespoň pro 2 talíře (nezpomeňte vzít v úvahu velikost
//         *  platnet). Nechete-li volit umístění plaent zcela náhodné,
//         *  můžete je mít někde předdefinované.
//         *  Planety nesmějí být ve spodním startovacím pásu ani v horním pásu
//         *  s hangáry; mezi planetami a hangáry musí zůstat manévrovací prostor.
//         */
//        for(int i=0; i < planeta.length; i++) {
//            planeta[i] = new Planeta(mezi(25, 275), mezi(25, 250));
//            planeta[i].nakresli();
//        }
//%I-
        V.překresli();
        start();
    }


//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================
//%I+ <0    //Tohle nejspíš opravdu nepotřebuju
//
//    /***************************************************************************
//     * Nastaví používání UFO, jež budou instancemi třídy UFO.
//     */
//    public void použijUFO()
//    {
//        použijTřídu( UFO.class );
//    }
//
//
//    /***************************************************************************
//     * Nastaví používání UFO, jež budou instancemi třídy UFO.
//     */
//    public void použijUFO_4()
//    {
//        použijTřídu( UFO_4.class );
//    }
//
//
//    /***************************************************************************
//     * Nastaví používání UFO, jež budou instacemi třídy zadané v parametru.
//
//     *
//     * @param ufoClass  Class objekt třídy, jajíž instance budou
//     *                  použity jako UFO.
//     */
//    public void použijTřídu( Class<? extends UFO> ufoClass )
//    {
//        if( this.ufoClass == ufoClass ) {
//            return;
//        }
//        try
//        {
//            ufoKonstruktor = ufoClass.getConstructor(
//                new Class[] { Talíř.class, int.class } );
//        }
//        catch( Exception e )
//        {
//            throw new RuntimeException(
//                "Nepodařilo se vytvořit konsturktor třídy " + ufoClass, e );
//        }
//        if( ! UFO.class.isAssignableFrom( ufoClass ) ) {
//            throw new RuntimeException(
//                "Třída " + ufoClass + " není potomkem třídy UFO" );
//        }
//        this.ufoClass = ufoClass;
//    }
//
//%I-

    /***************************************************************************
     * Vytvoří instanci UFO, umístí jí na startovní rampu
     * v levém dolním rohu plátna zobrazujícího vesmír a vrátí odkaz na ni.
     *
     * @return Odkaz na vytvořené UFO připravené na startovací rampě.
     */
    public IUFO přistavUFO()
    {
        /*# Pro talíř je třeba spočítat správnou pozici na startovní rampě. */
        Talíř talíř = new Talíř( VELIKOST, VELIKOST );
        ovládané = továrna.newUFO(talíř, ++početUFO);
        /* Zařazení do seznamu musí být synchronizováno, aby k němu nemohlo
         * dojít uprostřed procházení seznamem při překreslování vesmíru. */
        synchronized( ufoList ) {
            ufoList.add( ovládané );
        }
        V.překresli();
        return ovládané;
    }


    /***************************************************************************
     * Ukončí multipřesun a tím uvolní procesor.
     */
    public void stop()
    {
        film.cancel();
        stojí = true;
        if( posluchač != null )
        {
            V.odhlašKlávesnici( posluchač );
            posluchač = null;
        }
    }


    /***************************************************************************
     * Spustí nový multipřesun.
     */
    public void start()
    {
        if( stojí )
        {
            //perioda uchováv počet milisekund mezi dvěma snímky
            int perioda = 1000 / FREKVENCE;
            film = new Film();
            //timer.scheduleAtFixedRate(film, perioda, perioda );
            timer.schedule(film, perioda, perioda);
            stojí = false;
            if( zKlávesnice )
            {
                posluchač = new Posluchač();
                V.přihlašKlávesnici( posluchač );
            }
        }
    }



//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//%I+ <0
//
//    /** Generátor náhodných čísel slouží pouze následující metodě. */
//    private static final java.util.Random rnd = new java.util.Random();
//
//    /***************************************************************************
//     * Vrátí náhodné číslo v zadaném rozsahu, tj. od spodní meze včetně
//     * do horní meze nevčetně.
//     *
//     * @param od Dolní mez, tj. nejmenší generovatelné číslo.
//     * @param k  Horní mez, tj. nejmenší už negenerovatelné číslo.
//     */
//    private static int mezi( int od, int k )
//    {
//        return od + rnd.nextInt( k-od );
//    }
//
//
//    /***************************************************************************
//     * Pomocná metoda pro test chování klávesnice.
//     */
//    private static void vypiš( KeyEvent e )
//    {
//        char kch = e.getKeyChar();
//        char ch = ((32<=kch)&&(kch<=0xff)) ? kch : '?';
//        int kc=e.getKeyCode();
//        System.out.println(": znak='" + ch +
//          "', kódZnaku=" + (int)kch +
//          ", kód=" + kc +
//          ", loc=" + e.getKeyLocation() +
//          ", txt=" + KeyEvent.getKeyText(kc) );
//    }
//
//
//
//%I-
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Zkontroluje, je-li UFO v nějakém hangáru a 
     * je-li jeho rychlost menší než 10, tak je v hangáru vystředí
     * a nastaví jeho rychlost i tah na nulu.
     *
     * @param ufo Kontrolované UFO.
     */
    private void zkontroluj( IUFO ufo )
    {
        //Dostalo se UFO do oblasti hangárů?
        if( Math.abs(ufo.getY() - yHangáru) > VELIKOST/2 ) {
            return;
        }
        //Letí dostatečně pomalu?
        if( (Math.abs(ufo.getXRychlost()) + Math.abs(ufo.getYRychlost())) > VELIKOST ) {
            return;
        }
        //Nalétá-li do některého hangáru, požádej hangár, ať je zaparkuje
        double ux = ufo.getX();
        for( int ir=rampa.length;   --ir > 0;   )
        {
            if( Math.abs(rampa[ir].getX() - ux) <= VELIKOST/2 )
            {
                rampa[ir].zaparkuj( ufo );
                break;
            }
        }
    }



//== INTERNÍ DATOVÉ TYPY =======================================================

    //==========================================================================
    /***************************************************************************
     * Společný rodič všech čtverců a elips.
     */
    static abstract class Tvar
    {
        double xPos;    //Bodová x-ová souřadnice instance
        double yPos;    //Bodová y-ová souřadnice instance
        int    rozměr;  //velikost v bodech
        Barva  barva;   //Barva instance

        Tvar( double x, double y, int rozměr, Barva barva )
        {
            this.xPos   = x;
            this.yPos   = y;
            this.rozměr = rozměr;
            this.barva  = barva;
        }


        /***********************************************************************
         * Nakreslí svoji instanci.
         */
        public abstract void nakresli();


        /***********************************************************************
         * Vrátí x-ovou souřadnici pozice instance.
         *
         * @return  x-ová souřadnice.
         */
        public double getX()
        {
            return xPos;
        }


        /***********************************************************************
         * Vrátí y-ovou souřadnici pozice instance.
         *
         * @return  y-ová souřadnice.
         */
        public double getY()
        {
            return yPos;
        }


        /***********************************************************************
         * Nastaví novou pozici instance.
         *
         * @param x   Nová x-ová pozice instance
         * @param y   Nová y-ová pozice instance
         */
        public void setPozice( double x, double y )
        {
            xPos = x;
            yPos = y;
        }


        /***********************************************************************
         * Vrátí barvu instance.
         *
         * @return  Instance třídy Barva definující nastavenou barvu.
         */
        public Barva getBarva()
        {
            return barva;
        }


        /***********************************************************************
         * Nastaví novou barvu instance.
         *
         * @param nová  Požadovaná nová barva.
         */
        public void setBarva(Barva nová)
        {
            barva = nová;
            nakresli();
        }

    }


    //==========================================================================
    /***************************************************************************
     * Ekvivalent třídy Elipsa, pouze s nejnutnějším počtem metod.
     *
     * @return Název třídy následovnaý podtržítkem a "rodným číslem" instance.
     */
    static class Talíř extends Tvar implements ITalíř
    {
        Ellipse2D.Double kruh;

        /***********************************************************************
         * Vytvoří instanci o zadaných parametrech.
         */
        Talíř(double x, double y)
        {
            super( x, y, VELIKOST, Barva.ČERVENÁ );
            int r2 = rozměr >> 1;   //Poloviční rozměr
            kruh = new Ellipse2D.Double(
                (int)xPos-r2, (int)yPos-r2, rozměr, rozměr );
        }


        /***********************************************************************
         * Nakreslí svoji instanci.
         * Při kreslení považuje za svoji pozici střed kruhu.
         */
        @Override
        public void nakresli()
        {
            V.setBarvaPopředí( barva );
            V.zaplň( kruh );
        }


        /***********************************************************************
         * Nakreslí svoji instanci.
         * Při kreslení považuje za svoji pozici střed kruhu.
         */
        @Override
        public void setPozice( double x, double y )
        {
            super.setPozice( x, y );
            int r2 = rozměr >> 1;   //Poloviční rozměr
            kruh.setFrame( (int)xPos-r2, (int)yPos-r2, rozměr,  rozměr );
        }

    }//static class Kruh extends Tvar


//%I+ <0
//
//    //==========================================================================
//    /***************************************************************************
//     * Instance třídy představují planety, kterým se ufo musí vyhýbat.
//     */
//    private static class Planeta extends Kruh
//    {
//        /***********************************************************************
//         * Vytvoří planetu na zadaných souřadnicích.
//         */
//        Planeta( int x, int y )
//        {
//            super( 0, 0, mezi( NEJMENŠÍ, NEJVĚTŠÍ ),
//                   barvyPlanet[ mezi(0,barvyPlanet.length)]  );
//            /*# Najít takovou pseudonáhodnout pozici,
//             *  aby planeta byla od všech dříve vygenerovaných planet
//             *  dostatečně daleko - tj. aby mezi planetami bylo místo alespoň
//             *  pro 2 talíře (nezpomeňte vzít v úvahu velikost platnet).
//             *  Nepodaří-li se takové místo najít, ohlásí chybu. */
//            nakresli();
//        }
//
//    }//private static class Planeta extends Kruh
//
//
//%I-

    //==========================================================================
    /***************************************************************************
     * Instance třídy Film jsou úlohy simulující život vesmíru.
     * Film je definván jako samostatná třída proto, aby jej bylo možno
     * snadno zapnout a vypnout.
     */
    private class Film extends TimerTask
    {
        /***********************************************************************
         * Metoda vyžadovaná rozhraním Runable implementovaným rodičovskou
         * třídou TimerTask - tuto metodu zavolá Timer pokaždé,
         * když se rozhodne spustit další provedení opakovaného úkolu
         * (Timertask) = multipřesunu.
         */
        @SuppressWarnings( "empty-statement" )
        @Override
        public void run()
        {
            V.smaž();
            for( int i=  rampa.length;  --i >= 0;    rampa[i].nakresli() );
//%I+ <0
//            for( int i=planeta.length;  --i >= 0;  planeta[i].nakresli() );
//%I-
            //Při překreslování se nesmí měnit počet objektů v seznamu
            synchronized( ufoList )
            {
                for( int i=ufoList.size();      --i >= 0;  )
                {
                    IUFO u = ufoList.get( i );
                    u.popojeď( FREKVENCE );
                    u.nakresli();
                    zkontroluj( u );
                }
            }//synchronized
            V.překresli();
       }//public void run()

    }//private class Film extends TimerTask



    //==========================================================================
    /***************************************************************************
     * Instance třídy Posluchač slouží jako posluchač klávesnice
     * řídící pohyb UFO v druhé variantě hry.
     */
    private class Posluchač extends KeyAdapter
    {
        /***********************************************************************
         * Ošetření klávesnice.
         */
        @Override
        public void keyPressed(KeyEvent e)
        {
            //vypiš( e );
            int kc = e.getKeyChar();
            if( ('1' <= kc)  &&  (kc <= '9') )
            {
                int iu = kc - '1';  //1. ufo bude v 0. prvku
                if( iu < ufoList.size() ) {
                    ovládané = ufoList.get(iu);
                }
                return;
            }
            kc = e.getKeyCode();
            if( kc == KeyEvent.VK_ENTER )
            {
                přistavUFO();
                return;
            }
            if( ovládané == null ) {
                return;
            }
            switch( kc )
            {
                case KeyEvent.VK_DOWN:
                    ovládané.dolů();
                    return;

                case KeyEvent.VK_LEFT:
                    ovládané.vlevo();
                    return;

                case KeyEvent.VK_RIGHT:
                    ovládané.vpravo();
                    return;

                case KeyEvent.VK_UP:
                    ovládané.vzhůru();
                    return;

                case KeyEvent.VK_SPACE:
                    ovládané.vypniMotory();
                    return;
            }//switch
        }

    }//private class Posluchač extends KeyAdapter



//== TESTY A METODA MAIN =======================================================
//%X+

    public static void main( String[] args ) {
        //Následující přetypování si vyžádala NB, protože nechtěla pochopit,
        //že továrna implementuje své rozrhaní
        ITovárnaNaUFO tnu = (ITovárnaNaUFO)new TovárnaNaUFO();
        Dispečer d = getDispečer(tnu);
        IUFO ufo = d.přistavUFO();
        d.start();
        ufo.setRychlost( 10, 10 );
    }

//%X-
}//public class Dispečer
